﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using iStudio.Structure.Wave;

namespace iStudio.Structure.Control.Audio.Editor
{
    public class EditorCachedChunk
    {
        /// <summary>
        /// Size of each frame for waveform generation
        /// </summary>
        protected readonly WaveChunk Chunk;
        protected readonly EditorCore EditorCore;
        protected readonly WaveTrack Track;

        private float[] _waveformData;

        /// <summary>
        /// Constructor creates cached chunk object
        /// </summary>
        public EditorCachedChunk (WaveTrack track, WaveChunk chunk, EditorCore editorCore)
        {
            Track = track;
            Chunk = chunk;
            EditorCore = editorCore;

            Chunk.WaveBuffer.CollectionChanged += WaveBufferOnCollectionChanged;
            Chunk.PropertyChanged += ChunkOnPropertyChanged;

            GenerateWaveform ();
        }

        /// <summary>
        /// Sets canvas property of elements
        /// </summary>
        /// <param name="property">property set</param>
        /// <param name="value"></param>
        public virtual void SetCanvasProperty(EditorCanvasProperty property, double value)
        {

        }

        /// <summary>
        /// Protected accessor for waveform data
        /// </summary>
        protected float[] WaveformData
        {
            get
            {
                return _waveformData;
            }
        }

        /// <summary>
        /// If chunk property changes such as offset, channel number or selection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="propertyChangedEventArgs"></param>
        protected virtual void ChunkOnPropertyChanged (object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
        }

        /// <summary>
        /// Recalculates waveform frame data on wave collection changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="notifyCollectionChangedEventArgs"></param>
        protected virtual void WaveBufferOnCollectionChanged (object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
            //
            // Handle WaveformData modifications on buffer collection changed
            //

            // Value was changed, e.g. changed to new value
            if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Replace)
            {
                //
                // Replace puts new items into NewItems collection
                //

                if (notifyCollectionChangedEventArgs.NewItems.Count > 0)
                {
                    //
                    // On replace, the collection is locked to framesize if module is changing all values
                    //

                    //
                    // Calculate the starting and ending position of the frame
                    //

                    int frameStart = (int)Math.Floor(notifyCollectionChangedEventArgs.NewStartingIndex / (double)EditorCore.RenderFrameSize);
                    frameStart = frameStart * EditorCore.RenderFrameSize;
                    int frameEnd = frameStart + EditorCore.RenderFrameSize;

                    float max = float.MinValue;

                    for (int i = frameStart; i < Math.Min (frameEnd, Chunk.WaveBuffer.Count); i++)
                    {
                        if (Chunk.WaveBuffer[i] > max)
                        {
                            max = Chunk.WaveBuffer[i];
                        }
                    }

                    //
                    // Change max of calculated frame
                    //

                    int index = (int)Math.Floor(notifyCollectionChangedEventArgs.NewStartingIndex / (double)EditorCore.RenderFrameSize);

                    _waveformData[index] = max;
                }
            }

            //
            // Value was added, usually with recording
            //
            if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Add)
            {
                //
                // Check if we need to add another frame to waveform data, then we will
                //

                int blockCount = (int)Math.Ceiling(Chunk.Length / (double)EditorCore.RenderFrameSize);

                // If we DO need to add another frame, we add frame, AND recalculate last one
                if (_waveformData.Length < blockCount)
                {
                    float[] newWaveform = new float[blockCount];

                    //
                    // Recalculate last frame, not new one, IF we have one
                    //

                    if (_waveformData.Length > 0)
                    {
                        int frameStart = (int)Math.Floor(Chunk.WaveBuffer.Count / (double)EditorCore.RenderFrameSize);
                        frameStart = frameStart * EditorCore.RenderFrameSize;
                        int frameEnd = frameStart + EditorCore.RenderFrameSize;

                        float max = float.MinValue;

                        for (int i = frameStart; i < Math.Min (frameEnd, Chunk.WaveBuffer.Count); i++)
                        {
                            if (Math.Abs (Chunk.WaveBuffer[i] - max) > float.Epsilon)
                            {
                                max = Chunk.WaveBuffer[i];
                            }
                        }

                        if (max > 1.0)
                        {
                            max = 1.0f;
                        }

                        _waveformData[_waveformData.Length - 1] = max;
                    }

                    //
                    // Copy old data into new waveform
                    //

                    Array.Copy (_waveformData, newWaveform, _waveformData.Length);

                    //
                    // Store new array into waveformData
                    //

                    _waveformData = newWaveform;
                }
            }

            // Value was removed
            if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Remove)
            {
                //
                // Calculate block count after removal, if we need to recalculat
                //

                int blockCount = (int)Math.Ceiling(Chunk.Length / (double)EditorCore.RenderFrameSize);

                // If we DO need to delete the frame
                if (_waveformData.Length > blockCount)
                {
                    float[] newWaveform = new float[blockCount];

                    //
                    // Copy array data into new waveform and make sure the correct frame is removed
                    //

                    int frameIndex = (int)Math.Floor(notifyCollectionChangedEventArgs.OldStartingIndex / (double)EditorCore.RenderFrameSize);

                    //
                    // Copy first part of the data
                    //

                    Array.Copy (_waveformData, newWaveform, frameIndex);

                    //
                    // Copy second part of the data
                    //

                    if ( (frameIndex + 1) < _waveformData.Length)
                    {
                        Array.Copy (_waveformData, frameIndex + 1, newWaveform, frameIndex, _waveformData.Length - frameIndex);
                    }

                    _waveformData = newWaveform;
                }
            }
        }

        private void GenerateWaveform ()
        {
            //
            // Much simplier waveform generation -> just calculate maxes of all blocks
            //

            int blockCount = (int)Math.Ceiling(Chunk.Length / (double)EditorCore.RenderFrameSize);

            _waveformData = new float[blockCount];

            float max = float.MinValue;

            int frame = 0;

            //
            // TODO: Paralellization on the loop for each frame
            //

            for (int i = 0; i < Chunk.WaveBuffer.Count; i++, frame++)
            {
                if (Chunk.WaveBuffer[i] > max)
                {
                    max = Chunk.WaveBuffer[i];
                }

                //
                // End of frame
                //

                if (frame == EditorCore.RenderFrameSize)
                {
                    frame = 0;

                    // Calculate index
                    int index = (int)Math.Floor(i / (double)EditorCore.RenderFrameSize);

                    _waveformData[index] = max;

                    max = 0;
                }
            }

            //
            // Last frame might be shorter
            //

            if (frame > 0)
            {
                _waveformData[_waveformData.Length - 1] = max;
            }
        }
    }
}
